<?php
require_once("Xiachufang/util/Logger.php");
require_once("Xiachufang/util/EntityCache.php");
require_once("Xiachufang/util/MainConfig.php");
require_once("Xiachufang/util/DbMgr.php");

interface Entity {
	/*
	 * 根据id加载一个对象，如果数据不存在则抛出异常。
	 * 如果对象所映射的数据表是联合主键，则可以使用id数组传入联合主键值。
	 */
	function load($id);
	/*
     * 根据id获取一个对象，返回true或者false。
     * 如果对象所映射的数据表是联合主键，则可以使用id数组传入联合主键值。
     */
    function get($id);
    /*
     * 把此对象的数据保存。
     * 如果对象保存后会自动产生id值，则此方法会获取生成的id值。
     */
    function save();
    /*
     * 根据当前对象的id删除此对象。
     */
    function delete();
    /*
     * 根据当前对象的id更新对象的相关属性。
     * 只有属性被修改才会更新。
     */
    function update();

    /*
     * 判断此对象是否可被缓存。
     */
    function isCacheable();
    /*
     * 获取此对象缓存的过期时间。
     */
    function getCacheExpireTime();
}


abstract class DBEntity implements Entity {

    const NORMAL_SAVE = 0;
    const DUPLICATE_REPLACE = 1;
    const DUPLICATE_IGNORE = 2;

    protected $_old = array();

    protected abstract function getPropertyMap();

    public abstract function getTable();

    public abstract function getColumnList();

    public abstract function getPrimaryKey();

    protected function getLog() {
        return Logger::getLogger("Entity_".$this->getTable());
    }

    /**
     * 取得操作master数据库的mysql对象.
     * @todo 根据是否在事务中来决定要使用哪种db
     * @return Mysql mysql
     */
    public function getMasterDb() {
        return DBMgr::getMaster($this->getTable());
    }

    /**
     * 取得操作slave数据库的mysql对象.
     * @todo 根据是否在事务中来决定要使用哪种db
     * @return Mysql mysql
     */
    public function getSlaveDb() {
        return DBMgr::getSlaver($this->getTable());
    }

    /**
     * 加载对就主键数据.
     *
     * @param mixed $id
     * @throws DataAccessException 如果数据不存在抛出异常
     */
    public function load($id) {
        $result = $this->get($id);
        if ($result==false) {
            throw new DataAccessException("目标数据不存在");
        }
    }

    protected function isCacheById() {
        return true;
    }

    protected function buildCacheKey($id=NULL) {
        if ($id===NULL) {
            $id = $this->_getId();
        }
        //联合主键，传入数组参数
        if (is_array($id)) {
            $id = implode("_",$id);
        }
        return $this->getTable()."_".$id;
    }

    /**
     * 取得对应主键的数据.
     * @return bool 数据存在返回true,否则返回失败.
     */
    public function get($id) {
    	if (!$id) return false;
        if (is_object($id)) {
            throw new Exception("oops");
        }
        $table = $this->getTable();
        $columnList = $this->getColumnList();
        if ($this->isCacheable()) {
            //如果Entity被缓存了
            $cacheKey = $this->buildCacheKey($id);
            $cacheResult = EntityCache::get($cacheKey);
            if ($cacheResult !== false) {
            	$this->installCache($cacheResult);
            	return true;
            }
        }
        $primaryKey = $this->getPrimaryKey();
        $sql = "select ".implode(",",$columnList)." from $table ".$this->_buildWhere($primaryKey)." limit 0,1";
        if (!is_array($id)) { //处理联合主键
            $id = array($id);
        }
        $params = $id;
        $db = $this->getSlaveDb();
        $row = $db->queryUnique($sql,$params);
        if ($row==NULL) {
            return false;
        }
        $this->installData($row);

        return true;
    }

    public function findBy($field,$value){
        $table=$this->getTable();
        //根据主键之外的字段缓存
        $key = $table."_".$field."_".$value;
        $primary = EntityCache::get($key);
        if ($primary) {
        	return $this->get($primary);
        }
        $columnList = $this->getColumnList();
        $sql="select ".implode(",",$columnList)." from $table where $field=? limit 0,1";
        $db = $this->getSlaveDb();
        $row = $db->queryUnique($sql,array($value));
        if($row==NULL) {
            return false;
        }
        else {
        	$primaryKey = $this->getPrimaryKey();
        	if (is_string($primaryKey)){
        		$primary = $row[$primaryKey];
        	} else {
        		$primary = array();
        		foreach($primaryKey as $pk){
        			$primary[] = $row[$pk];
        		}
        	}
        	//缓存其他字段到主键字段的关系
        	EntityCache::set($key,$primary,0,$this->getCacheExpireTime());
            $this->installData($row);
            return true;
        }
    }

    function installData($row,$cache=true) {
        if (count($this->_old)>0) {
            $this->_old = array();
        }
        $map = $this->getPropertyMap();
        $columnList = $this->getColumnList();
        foreach ($columnList as $column) {
            $property = $map[$column];
            if (isset($row[$column])) {
                $this->$property = $row[$column];
                $this->_old[$property] = $row[$column];
            }
        }
        if ($cache && $this->isCacheable()) {
            $cacheKey = $this->buildCacheKey();
            EntityCache::set($cacheKey,$this->_old,0,$this->getCacheExpireTime());
        }
    }

    /*
     * 判断一个Entity是否已在缓存中
     */
    function isCached($id){
        if (is_object($id)) {
            throw new Exception("oops");
        }
        $cacheKey = $this->buildCacheKey($id);
        return EntityCache::get($cacheKey)!==false;
    }

    /**
     * 将缓存中的数据重新装入Entity.
     */
    function installCache($data) {
        foreach ($data as $k=>$v) {
            $this->$k = $v;
            $this->_old[$k] = $v;
        }
    }

    /**
     * 保存数据.
     * @return void
     * @throws DataAccessException 保存失败抛出
     */
    public function save($duplicate=DBEntity::NORMAL_SAVE) {
        $table = $this->getTable();
        $columnList = $this->getColumnList();
        $primaryKey = $this->getPrimaryKey();
        if (!is_array($primaryKey)){
            $primaryKey = array($primaryKey);
        }
        $map = $this->getPropertyMap();
        $sql = "";
        $sql2 = "";
        $params = array();
        $size = count($columnList);
        $allStored = true;
        //如果是联合主键，则标示主键已赋值
        $isPrimaryValueSet = count($primaryKey) > 1;
        if ($isPrimaryValueSet === false) {
            //如果不是联合主键，则判断是否已赋值
            $primaryProperty = $map[$primaryKey[0]];
            $isPrimaryValueSet = isset($this->$primaryProperty);
        }
        for($i=0;$i<$size;$i++) {
            $column = $columnList[$i];
            $property = $map[$column];
            if (!isset($this->$property)) {
            	/*
                if (array_search($column, $primaryKey) === false) {
                    $allStored = false;
                }
				*/
                continue;
            }
            $sql .= $column."=?,";
            $sql2 .= $column."=values($column),";
            $params[] = $this->$property;
            $this->_old[$property] = $this->$property;
        }
        if (count($params)==0) {
            throw new DataAccessException("没有数据需要保存");
        }
        $sql = substr($sql,0,strlen($sql)-1);
        $sql2 = substr($sql2,0,strlen($sql2)-1);
        //如果需要替换已存在的数据
        if ($duplicate == DBEntity::DUPLICATE_REPLACE){
            $sql = "insert into $table set $sql on duplicate key update $sql2";
        } elseif ($duplicate == DBEntity::DUPLICATE_IGNORE){
            $sql = "insert ignore into $table set $sql";
        } else {
            $sql = "insert into $table set $sql";
        }
        $db = $this->getMasterDb();
        $rs = $db->query($sql,$params);
        if (!$rs->isSuccess()) {
            throw new DataAccessException("保存数据失败");
        }
        if (!$isPrimaryValueSet) {
            $this->$primaryProperty = $db->getLastInsertId();
            $this->_old[$primaryProperty] = $this->$primaryProperty;
        }
        if ($allStored && $rs->isSuccess() && $this->isCacheable()) {
            $cacheKey = $this->buildCacheKey();
            EntityCache::set($cacheKey,$this->_old);
        }
    }

    /*
     * 获取当前对象的主键值，返回按照$primaryKey顺序的键值数组
     */
    protected function _getId() {
        $primaryKey = $this->getPrimaryKey();
        $map = $this->getPropertyMap();
        if (!is_array($primaryKey)) {
            $primaryKey = array($primaryKey);
        }
        $ret = array();
        //处理联合主键的键值
        foreach($primaryKey as $v) {
            $property = $map[$v];
             
            if (isset($this->_old[$property])) {//保证更新主键的情况能正常工作
                $pv =$this->_old[$property];
            } else {
                $pv = $this->$property;
            }
            if (!$pv){
				
				$oldValue = '';
				foreach ($this->_old as $k=>$kv) {
					$oldValue .= "$k = ".$kv.", ";
				}
				$this->getLog()->error("missing id : primaryKey=[".implode($primaryKey,"],").",property=$property, _old=[$oldValue]");
                throw new FeedException("missing id!");
            }
            $ret[] = $pv;
        }
        return $ret;
    }

    public function delete() {
        $table = $this->getTable();
        $columnList = $this->getColumnList();
        $primaryKey = $this->getPrimaryKey();
        $id = $this->_getId();
        $sql = "delete from $table ".$this->_buildWhere($primaryKey)." limit 1";
        $db = $this->getMasterDb();
        $rs = $db->query($sql,$id);
        if (!$rs->isSuccess()) {
            throw new DataAccessException("删除数据失败");
        }
        $this->_old = array();
        if ($this->isCacheable()) {
            $cacheKey = $this->buildCacheKey();
            EntityCache::delete($cacheKey);
        }
        return $rs->isSuccess();
    }

    /**
     * 更新修改过的属性.
     *
     * @return void
     */
    public function update() {
        $table = $this->getTable();
        $columnList = $this->getColumnList();
        $primaryKey = $this->getPrimaryKey();
        $id = $this->_getId();
        $map = $this->getPropertyMap();
        $changed = false;
        $sql = "update $table set ";
        $params = array();
        foreach ($columnList as $column) {
            $property = $map[$column];
            unset($old);
            if (isset($this->_old[$property])) {
                $old = $this->_old[$property];
            }
            $new = $this->$property;
            if ((isset($old) && $old!=$new) || (!isset($old) && isset($new))) {
                $changed = true;
                $sql .= " $column=?,";
                $params[] = $new;

                $this->_old[$property] = $new;
            }
        }
        if ($changed===false) {
            return true;
        }
        //先缓存再更新
        if ($this->isCacheable()) {
            $cacheKey = $this->buildCacheKey();
            EntityCache::set($cacheKey,$this->_old,0,$this->getCacheExpireTime());
        }
        $sql = substr($sql,0,strlen($sql)-1);
        $sql .= $this->_buildWhere($primaryKey)." limit 1";
        $params = array_merge($params,$id);
        $db = $this->getMasterDb();
        $rs = $db->execute($sql,$params);
        if (!$rs->isSuccess()) {
            //更新失败清除缓存
            if ($this->isCacheable()) {
                $cacheKey = $this->buildCacheKey();
                EntityCache::delete($cacheKey,1);
            }
            throw new DataAccessException("更新数据失败");
        }
        return $rs->isSuccess();
    }

    public function isCacheable() {
        return true;
    }

    public function getCacheExpireTime() {
        return 600;
    }

    /**
     * 将数据组织成数组形式返回.
     * @param $specialPropertyList 要获取的属性列表,如为NULL,则返回全部属性
     * @return array
     */
    public function getDataArray($specialPropertyList=NULL) {
        $data = array();
        $columnList = $this->getColumnList();
        $map = $this->getPropertyMap();
        if ($specialPropertyList!=NULL) {
            foreach ($specialPropertyList as $property) {
                $data[$property] = $this->$property;
            }
        }
        else {
            foreach (array_values($map) as $property) {
                $data[$property] = $this->$property;
            }
        }
        return $data;
    }

    /**
     * 获取此实体对象的原始数据
     */
    public function getRawData(){
        $data = array();
        $columnList = $this->getColumnList();
        $map = $this->getPropertyMap();
        foreach ($columnList as $col) {
            $property = $map[$col];
            $data[$col] = $this->$property;
        }
        return $data;
    }
    /**
     * 过滤掉部分属性.
     *
     * @param array $filterPropertyList 要过滤的属性列表
     * @return array
     */
    public function filterDataArray($filterPropertyList=NULL) {
        if ($filterPropertyList == NULL || !is_array($filterPropertyList)){
            $filterPropertyList = array();
        }
        $data = array();
        $columnList = $this->getColumnList();
        $map = $this->getPropertyMap();
        $properties = array_values($map);
        $properties = array_diff($properties,$filterPropertyList);
        foreach ($properties as $property) {
            $data[$property] = $this->$property;
        }
        return $data;
    }

    /**
     * 将主键为$id的entity从缓存中清除.
     *
     * @param string $id
     * @return bool
     */
    public function kick($id) {
        $cacheKey = $this->buildCacheKey($id);
        return EntityCache::delete($cacheKey);
    }

    /**
     * 将此entity从缓存中清除.
     *
     * @return bool
     */
    public function clearCache() {
        if ($this->isCacheable()) {
            $cacheKey = $this->buildCacheKey();
            return EntityCache::delete($cacheKey);
        }
        return true;
    }

    /**
     * 复制已设置的属性值到另一个对象,忽略没有设置值的属性.
     *
     * @param DBEntity $otherEntity
     * @return int $count 复制属性数量
     */
    public function copy($otherEntity) {
        $columnList = $this->getColumnList();
        $map = $this->getPropertyMap();
        $count = 0;
        foreach ($columnList as $column) {
            $property = $map[$column];
            if (isset($this->$property)) {
                $otherEntity->$property = $this->$property;
                $count++;
            }
        }
        return $count;
    }

    private function _buildWhere(&$primaryKey){
        if (!is_array($primaryKey)){
            $primaryKey = array($primaryKey);
        }
        $sql = " where ";
        for($i=0; isset($primaryKey[$i]); $i++) {
            if ($i == 0)
            $sql .= $primaryKey[$i]."=?";
            else
            $sql .= " and ".$primaryKey[$i]."=?";
        }
        return $sql;
    }
    
    //批量向表中添加数据
    static public function batchInsert($dataArray,$tableName,$isDuplicatesUpdate = false)
    {
    	$db = DBMgr::getMaster($tableName);
    	$db->batchInsert($dataArray,$tableName,$isDuplicatesUpdate);
    }
}

?>
